package net.pugsplace.gpremacy.server.net;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.pugsplace.gpremacy.packets.Depart;
import net.pugsplace.gpremacy.packets.Packet;
import net.pugsplace.gpremacy.packets.PlayerInformation;
import net.pugsplace.gpremacy.server.ClientListener;
import net.pugsplace.gpremacy.state.Player;

/** Maintains a stack of ClientListeners which react to incoming data packets */
public class Connection implements ClientListener {
	private static Logger log = Logger.getLogger(Connection.class.getName());
	private ObjectInputStream inStream;
	private final LinkedList<ClientListener> listeners;
	private ObjectOutputStream outStream;
	private final Player player;
	private final java.net.Socket socket;

	public Connection(java.net.Socket s) {
		player = new Player();

		listeners = new LinkedList<ClientListener>();
		listeners.addLast(this);

		socket = s;
		try {
			inStream = new ObjectInputStream(socket.getInputStream());
			outStream = new ObjectOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			log.log(Level.WARNING, "Exception creating streams", e);
		}

		new ProcessingThread(this).start();
	}

	public void clientDisconnected(Connection connectionObject) {
		// We don't care about ourselves being disconnected
	}

	public Player getPlayer() {
		return player;
	}

	/** Returns whether the connection is valid and connected */
	public boolean isAlive() {
		return socket.isConnected() && inStream != null && outStream != null;
	}

	public ClientListener peekListener() {
		return listeners.getLast();
	}

	public ClientListener popListener() {
		return listeners.removeLast();
	}

	public void pushListener(ClientListener cl) {
		listeners.addLast(cl);
	}

	/**
	 * @return a newly-received packet from the client, or null if there is some
	 *         problem.
	 */
	public boolean receiveAndProcess() {
		try {
			Object o = inStream.readObject();
			if (o instanceof Packet) {
				for (ClientListener cl : listeners) {
					if (cl.receivedPacket(this, (Packet) o) == true)
						break;
				}
				return true;
			}
		} catch (IOException e) {
			log.log(Level.WARNING, "Exception reading streams", e);
		} catch (ClassNotFoundException e) {
			log.log(Level.WARNING, "Exception translating object", e);
		}
		return false;
	}

	public boolean receivedPacket(Connection connectionObject, Packet packetObject) {
		if (packetObject instanceof PlayerInformation) {
			player.setName(((PlayerInformation) packetObject).playerName);
			return true;
		}
		if (packetObject instanceof Depart) {
			popListener();
			return true;
		}
		return false;
	}

	/** Kill this connection */
	private void setDead() {
		log.severe("Client connection for player " + player + " is now dead.");

		outStream = null;
		inStream = null;
		try {
			if (socket != null)
				socket.close();
		} catch (IOException e) {
			log.log(Level.WARNING, "Exception closing socket", e);
		}

		// Notify everyone this connection is dead
		for (ClientListener cl : listeners) {
			cl.clientDisconnected(this);
		}
	}

	@Override
	public String toString() {
		return "Connection for " + player + " on " + socket;
	}

	/** Sends a packet to the client */
	public void transmit(Packet packet) {
		try {
			outStream.writeObject(packet);
		} catch (IOException e) {
			setDead();
		}
	}

	/** Thread which processes incoming data */
	public class ProcessingThread extends Thread {
		Connection conn;

		public ProcessingThread(Connection parent) {
			conn = parent;
		}

		@Override
		public void run() {
			boolean contineProcessing = true;
			while (contineProcessing) {
				contineProcessing = receiveAndProcess();
			}
			setDead();
		}
	}
}
